/*
    Michael Eng, mse2124
    COMS 4156 HW1 main class
    Driver class that prompts the user to choose a question from HW1, then answers the user's question, prompting the user for necessary parameters.
*/

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

public class HW1 {
    public static final String COUNTRYSTATUSURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2006.html"; //Whatever named places are on this page should not be considered countries
    public static final String COUNTRYLOCATIONSURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2145.html"; //Page matching countries to continents/regions
    public static final String HAZARDSURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2021.html"; //Natural hazards page
    public static final String POLITICALPARTIESURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2118.html"; //Political parties page
    public static final String FLAGDESURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2081.html"; //Flag description page
    public static final String LANDBOUNDSURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2096.html"; //Land boundaries page
    public static final String COASTLINEURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2060.html"; //Coastline page
    public static final String CAPITALLOCATIONSURL = "https://www.cia.gov/library/publications/the-world-factbook/fields/2057.html"; //Capitals lat/long page

    public static void main(String[]args) throws Exception {

/*
    Before doing any other processing, we'll need to do two things.  First, get a list of territories so we can filter out non-country entities.  Second, match each country to a continent.
*/
        
        ArrayList<String> countryStatusPageSource = getCountryDataFromComparisonPages(COUNTRYSTATUSURL);
        //Hacky fix to force ignoring disputed regions of the world, plus the European Union
        countryStatusPageSource.add("European Union");
        countryStatusPageSource.add("Gaza Strip");
        countryStatusPageSource.add("Paracel Islands");
        countryStatusPageSource.add("Spratly Islands");
        countryStatusPageSource.add("West Bank");
        countryStatusPageSource.add("Western Sahara");


        HashMap<String, String> countryToContinentMap = new HashMap<String, String>();
        ArrayList<String> countryLocationPageSource = getCountryDataFromComparisonPages(COUNTRYLOCATIONSURL);
        ArrayList<String> allLocations = new ArrayList<String>();
        for(int i= 0; i< countryLocationPageSource.size()-1; i+=2) {
            String countryName = countryLocationPageSource.get(i);
            while (countryName.contains(":") && i < countryLocationPageSource.size() - 1) { //Hacky fix to deal with weird formatting
                i++;
                countryName = countryLocationPageSource.get(i);
            }
            if(i >= countryLocationPageSource.size()-1) { //Bounds check
                break;
            }
            allLocations.add(countryName);
            if(countryStatusPageSource.contains(countryName)) { //Skip if the place isn't an independent country
                continue;
            }
            String regionName = countryLocationPageSource.get(i+1);
            String continent = "";
            if(regionName.toUpperCase().contains("EUROPE")) {
                continent = "Europe";
            }
            else if(regionName.toUpperCase().contains("ASIA") || regionName.toUpperCase().contains("MIDDLE EAST")) {
                continent = "Asia";
            }
            else if(regionName.toUpperCase().contains("OCEANIA")) {
                continent = "Oceania";
            }
            else if(regionName.toUpperCase().contains("AFRICA")) {
                continent = "Africa";
            }
            else if(regionName.toUpperCase().contains("NORTH AMERICA") || regionName.toUpperCase().contains("CARIBBEAN")) {
                continent = "North America";
            }
            else if(regionName.toUpperCase().contains("SOUTH AMERICA")) {
                continent = "South America";
            }
            else {
                continent = "";
            }

            if(!continent.equals("")) { //Add country-continent mapping
                countryToContinentMap.put(countryName, continent);
            }
        }

/*
    Prompt user for question
*/
        System.out.println("Enter the question to answer:");
        System.out.println("1. Countries in a certain continent vulnerable to a certain natural hazard");
        System.out.println("2. Countries in a certain continent with more than a certain number of political parties");
        System.out.println("3. Countries with a certain color in their flag");
        System.out.println("4. Countries landlocked and surrounded entirely by only one country");
        System.out.println("5. Most capital cities visitable within a certain number of degrees latitude and longitude of each other");
        String userInput = (new BufferedReader(new InputStreamReader(System.in))).readLine().trim();
        int userInputNumber = Integer.parseInt(userInput);
        switch(userInputNumber) {
            case 1: //Natural hazard question
                String targetContinent = "";
                while(true) {
                    System.out.println("Enter a continent"); //Prompt for continent
                    String input = (new BufferedReader(new InputStreamReader(System.in))).readLine().trim();
                    if(continentInputValid(input)) {
                        targetContinent = input;
                        break;
                    }
                }
                String targetHazard = "";
                while(true) {
                    System.out.println("Enter a hazard type"); //Prompt for hazard type
                    String input = (new BufferedReader(new InputStreamReader(System.in))).readLine().trim();
                    if(!input.equals("")) {
                        targetHazard = input;
                        break;
                    }
                }
                ArrayList<String> hazardData = getCountryDataFromComparisonPages(HAZARDSURL);

                int index = 0;
                while(index < hazardData.size() - 1) { //Go through the scraped information
                    String country = hazardData.get(index);
                    if(countryToContinentMap.containsKey(country)) {
                        if(countryToContinentMap.get(country).equalsIgnoreCase(targetContinent)) { //Found a country in the desired continent, now go through subsequent lines looking for its hazard info
                            while(true) {
                                index++;
                                if(index >= hazardData.size() - 1) { //bound check
                                    break;
                                }
                                String hazardDataLine = hazardData.get(index);
                                if(allLocations.contains(hazardDataLine)) { //hit a new country's entry
                                    break;
                                }
                                else {
                                    if(hazardDataLine.contains(targetHazard.toLowerCase())) { //process line containing hazard data for the current country
                                        System.out.println(country + " has " + targetHazard);
                                        index++; //step to next line since this line was determined to be information about hazards for a particular country, rather than a country name
                                        break;
                                    }
                                }
                            }
                        }
                        else {
                            index++;
                        }
                    }
                    else {
                        index++;
                    }
                }
                break;
            case 2: //Political parties question
                targetContinent = "";
                while(true) {
                    System.out.println("Enter a continent"); //Prompt for continent
                    String input = (new BufferedReader(new InputStreamReader(System.in))).readLine().trim();
                    if(continentInputValid(input)) {
                        targetContinent = input;
                        break;
                    }
                }
                int targetNumParties = 0;
                System.out.println("Enter a number"); //Prompt for n, where number of political parties > n for a country to match
                userInput = (new BufferedReader(new InputStreamReader(System.in))).readLine().trim();
                targetNumParties = Integer.parseInt(userInput);
                ArrayList<String> partyData = getCountryDataFromComparisonPages(POLITICALPARTIESURL);
                index = 0;
                while(index < partyData.size() - 1) { //Iterate through scraped text
                    String country = partyData.get(index);
                    if(countryToContinentMap.containsKey(country)) {
                        if(countryToContinentMap.get(country).equalsIgnoreCase(targetContinent)) { //Found a matching country on the desired continent, go through subsequent lines to find its political party info
                            int numParties = 0;
                            while(true) {
                                index++;
                                if(index >= partyData.size() - 1) { //bound check
                                    break;
                                }
                                String partyDataLine = partyData.get(index);
                                if(allLocations.contains(partyDataLine)) { //hit a new country's entry
                                    break;
                                }
                                else { //process it, keep stepping until you hit a new country's entry
                                    if(partyDataLine.contains("none")) {
                                        index++;
                                        break;
                                    }
                                    else {
                                        numParties += (partyDataLine.split(";")).length;
                                    }
                                }
                            }
                            if(numParties > targetNumParties) {
                                System.out.println(country + " has " + numParties + " political parties");
                            }
                        }
                        else {
                            index++;
                        }
                    }
                    else {
                        index++;
                    }
                }
                break;
            case 3: //Flag color question
                String targetColor = "";
                while(true) { //Prompt user for color
                    System.out.println("Enter a flag color");
                    String input = (new BufferedReader(new InputStreamReader(System.in))).readLine().trim();
                    if(!input.equals("")) {
                        targetColor = input;
                        break;
                    }
                }
                ArrayList<String> flagData = getCountryDataFromComparisonPages(FLAGDESURL);
                index = 0;
                while(index < flagData.size() - 1) { //Iterate through scraped text from flag descriptions page
                    String country = flagData.get(index);
                    if(countryToContinentMap.containsKey(country)) { //If this entry is in fact a country
                        while(true) { //Iterate through subsequent lines until we find the country's actual flag description
                            index++;
                            if(index >= flagData.size() - 1) { //bound check
                                break;
                            }
                            String flagDataLine = flagData.get(index);
                            if(allLocations.contains(flagDataLine)) { //hit a new country's entry
                                break;
                            }
                            else {
                               if(flagDataLine.contains(" " +targetColor.toLowerCase())) { //process line containing flag data for the current country. space is because checking just "red" returns false positives if something like "declared" is in the line
                                    System.out.println(country + " has " + targetColor);
                                    index++; //step to next line since this line was determined to be information about hazards for a particular country, rather than a country name
                                    break;
                                }
                            }
                        }
                    }
                    else {
                        index++;
                    }
                }
                break;
            case 4: //Landlocked countries bordering only one country- should be Vatican City, Lesotho, San Marino
                ArrayList<String> borderData = getCountryDataFromComparisonPages(LANDBOUNDSURL);
                ArrayList<String> coastData = getCountryDataFromComparisonPages(COASTLINEURL);
                ArrayList<String> landlockedCountries = new ArrayList<String>();
                index = 0;
                //Get list of landlocked countries.
                while(index < coastData.size() - 1) {
                    String country = coastData.get(index);
                    if(countryToContinentMap.containsKey(country)) { //If this entry is in fact a country
                        String coastDataLine = coastData.get(index+1);
                        if(coastDataLine.contains("landlocked")) {
                            landlockedCountries.add(country);
                        }
                    }
                    index++;
                }

                //Cross-reference with countries that have only one border neighbor
                index = 0;
                while(index < borderData.size() - 2) {
                    String country = borderData.get(index);
                    if(landlockedCountries.contains(country)) {
                        String landBorderInfo = borderData.get(index+2); //If this country has land borders, the line immediately after the line with the country's name will consist of the total land border distance of that country and can be skipped for this question
                        if(landBorderInfo.contains("border countries")) {
                            int numBorderingCountries = (landBorderInfo.split(",")).length;
                            if(numBorderingCountries==1) {
                                System.out.println(country + " has only one bordering country and is landlocked");
                            }
                        }
                    }
                    index++;
                }
                break;
            case 5: // Latitude/longitude capital-maximizing question
                double targetDegrees = 0.0;
                while(true) { //Prompt user for degree range- bound with [0, 180] since we adjust latitude bounds to -90 and +90 at most anyway
                    System.out.println("Enter the degrees latitude/longitude range (must be between 0.0 and 180.0)"); //Prompt for n, where number of political parties > 10 for a country to match
                    userInput = (new BufferedReader(new InputStreamReader(System.in))).readLine().trim();
                    targetDegrees = Double.parseDouble(userInput);
                    if(targetDegrees < 0.0 || targetDegrees > 180.0) {
                        System.out.println("Invalid degree number entered, try again");
                    }
                    else {
                        break;
                    }
                }
                HashMap<String, XYCoordinate> capitalToLocationMap = new HashMap<String, XYCoordinate>();
                ArrayList<String> capitalData = getCountryDataFromComparisonPages(CAPITALLOCATIONSURL);
                index = 0;
                /*
                    Construct map of capital -> coordinates (signed based on direction)
                */
                while(index < capitalData.size()-1) {
                    String country = capitalData.get(index);
                    if(countryToContinentMap.containsKey(country)) {
                        if(capitalData.get(index+1).contains("no official capital")) { //Fix for Nauru. Apparently it doesn't have a capital.
                            index++;
                            continue;
                        }
                        String capital = (capitalData.get(index+1).split(":"))[1].trim(); //Capital name is from line immediately after country's name
                        if(capital.contains("note - the Old Royal Capital is Cetinje mentioned in the constitution")) { //Hacky fix for oddly-formatted Cetinje entry- without this conditional it still works, the text just displays as that sentence instead of just Cetinje
                            capital = "Cetinje";
                        }
                        index++;
                        while(true) { //Loop through subsequent lines until you find this capital's coordinates
                            index++;
                            if(index > capitalData.size() -1) {
                                break;
                            }
                            String capitalDataLine = capitalData.get(index);
                            if(capitalDataLine.contains("geographic coordinates")) { //Found capital's coordinates.  Convert them to decimal, store in the map, and break the loop.
                                String[] splitCoordinates = ((capitalDataLine.split(":"))[1]).trim().replaceAll(",", "").split(" "); // Array should look like [# # N/S # # W/E]
                                if(splitCoordinates.length != 6) { //Malformed coordinate splicing result, skip the entry
                                    index++;
                                    break;
                                }
                                XYCoordinate coord = new XYCoordinate(Double.parseDouble(splitCoordinates[0]), Double.parseDouble(splitCoordinates[1]), splitCoordinates[2], Double.parseDouble(splitCoordinates[3]), Double.parseDouble(splitCoordinates[4]), splitCoordinates[5]);
                                capitalToLocationMap.put(capital, coord);
                                index++;
                                break;
                            }
                        }
                    }
                    else {
                        index++;
                    }
                }
                /*
                    Figure out the square, N x N degrees, containing the most capitals.
                */
                double answerX = 0.0;
                double answerY = 0.0;
                int answerNumCapitals = 0;
                ArrayList<String> answerCapitalList = new ArrayList<String>();
                targetDegrees /= 2.0; //That way each side of the square is targetDegrees in length
                for(double centerX = -180.0; centerX <= 179.0; centerX++) { //Longitude
                    for(double centerY = -90.0; centerY <= 90.0; centerY++) { //Latitude
                        double leftX = centerX - targetDegrees;
                        double rightX = centerX + targetDegrees;
                        double downY = centerY - targetDegrees;
                        double upY = centerY + targetDegrees;
                        /*
                            Adjust for possible wraparound of box bounds
                        */
                        if(leftX < -180.0) {
                            leftX = 180.0 + (leftX + 180.0); //Ex- -189 degrees becomes 171 degrees (189 W becomes 171 E)
                        }
                        if(rightX > 180.0) {
                            rightX = -180.0 + (rightX - 180.0); //189 E becomes 171 W)
                        }
                        if(downY < -90.0) { //Flatten to -90 since that's as far south as we can go and latitude doesn't wrap around like longitude
                            downY = -90.0;
                        }
                        if(upY > 90.0) { //Flatten to 90 since that's as far north as we can go and latitude doesn't wrap around like longitude
                            upY = 90.0;
                        }
                        Iterator<String> it = capitalToLocationMap.keySet().iterator();
                        ArrayList<String> currCapitalList = new ArrayList<String>();
                        while(it.hasNext()) {
                            String currCapital = it.next();
                            XYCoordinate coord = capitalToLocationMap.get(currCapital);
                            double currLong = coord.getLong();
                            double currLat = coord.getLat();
                            if(currLong <= rightX && currLong >= leftX && currLat >= downY && currLat <= upY) {
                                currCapitalList.add(currCapital);
                            }
                        }
                        if(currCapitalList.size() > answerNumCapitals) {
                            answerNumCapitals = currCapitalList.size();
                            answerCapitalList = currCapitalList;
                            answerX = centerX;
                            answerY = centerY;
                        }
                    }
                }
                String answerDisplay = "Answer coordinates: " + answerY;
                String latSign = "";
                if(answerY >0.0) {
                    latSign = " N";
                }
                else if(answerY < 0.0) {
                    latSign = " S";
                }
                String longSign = "";
                if(answerX >0.0) {
                    longSign = " E";
                }
                else if(answerX < 0.0) {
                    longSign = " W";
                }
                answerDisplay += latSign + ", " + answerX + longSign + ": " + answerNumCapitals + " capitals";
                System.out.println(answerDisplay);
                System.out.println("Capital list: " + answerCapitalList);
                break;
            default:
                System.out.println("Invalid option entered, exiting...");
                break;
        }

        System.exit(0);
    }

/*
    Request one of the pages comparing a field across multiple countries
*/
    public static ArrayList<String> getCountryDataFromComparisonPages(String urlString) throws Exception { 
        URL url = new URL(urlString);
        URLConnection u = url.openConnection();
        BufferedReader br = new BufferedReader(new InputStreamReader(u.getInputStream()));
        String out = br.readLine();
        ArrayList<String> pageSource = new ArrayList<String>();
        boolean isInArticleBody = false;
        int count = 0;

        while(out != null) {
            while(!isInArticleBody) { //Skip beginning parts of source until we hit divs containing information
                if(out == null) {//Make sure we don't run out of HTML source prematurely
                    System.out.println("Exiting early, ran out of HTML");
                    br.close();
                    return null;
                }
                if(out.contains("PRINT")) { //All CIA field comparison pages start the tables after this line- would need to find a better third-party DOM client to improve this
                    isInArticleBody = true;
                }
                out = br.readLine();
            }
            out = out.trim(); //Remove whitespace, don't bother printing blank lines
            out = out.replaceAll("\\<.*?\\>", ""); //Remove tags, regex pulled from http://www.dzone.com/snippets/java-regex-remove-html-tags
            if(out.contains("//")) {
                break;
            }
            if(!out.equals("")) {
                count++;
                if(count > 3) { //Skip first three lines before we actually hit the comparison table data
                    pageSource.add(out);
                }
            }
            out = br.readLine();
        }
        br.close();
        return pageSource;
    }

/*
    Validate that the input string is a continent, excluding Antarctica.
*/
    public static boolean continentInputValid(String input) {
        if(input == null) {
            return false;
        }
        String[] continents = {"Africa", "Asia", "Oceania", "North America", "South America", "Europe"};
        for(String c:continents) {
            if(input.equalsIgnoreCase(c)) {
                return true;
            }
        }
        return false;
    }

}
